package org.codefinger.dao.impl;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

import javax.sql.DataSource;

import org.codefinger.dao.impl.sql.Column;
import org.codefinger.dao.impl.sql.ColumnItem;
import org.codefinger.dao.impl.sql.Group;
import org.codefinger.dao.impl.sql.Having;
import org.codefinger.dao.impl.sql.Join;
import org.codefinger.dao.impl.sql.Order;
import org.codefinger.dao.impl.sql.Source;
import org.codefinger.dao.impl.sql.Union;
import org.codefinger.dao.impl.sql.Where;
import org.codefinger.dao.util.ArrayIterator;
import org.codefinger.dao.util.Lang;
import org.codefinger.dao.util.jdbc.JDBCUtil;

public abstract class Querier {

	protected DataSource	dataSource;

	protected boolean		isDistinct;

	protected Column		column;

	protected String[]		aliases;

	protected Source		from;

	protected Join			join;

	protected Where			where;

	protected Group			group;

	protected Having		having;

	protected Union			union;

	protected Order			order;

	public Querier(//
			DataSource dataSource,//
			boolean isDistinct, //
			Column column, //
			Source from, //
			Join join,//
			Where where, //
			Group group, //
			Having having, //
			Union union, //
			Order order,//
			String[] aliases) {

		super();
		this.dataSource = dataSource;
		this.isDistinct = isDistinct;
		this.column = column;
		this.from = from;
		this.join = join;
		this.where = where;
		this.group = group;
		this.having = having;
		this.union = union;
		this.order = order;
		this.aliases = aliases;
	}

	public int queryCount(QueryParam queryParam) {
		Column column = this.column;
		SQLVisitor visitor = new SQLVisitor(queryParam);
		boolean flag;
		if (isDistinct) {
			visitor.appendSQL("SELECT COUNT(1) FROM (SELECT DISTINCT ");
			column.visitSQL(visitor);
			flag = true;
		} else if (union != null || having != null) {
			visitor.appendSQL("SELECT COUNT(1) FROM (SELECT ");
			column.visitSQL(visitor);
			flag = true;
		} else {
			visitor.appendSQL("SELECT COUNT(1)");
			flag = false;
		}
		from.visit(visitor);
		if (join != null) {
			join.visit(visitor);
		}
		if (where != null) {
			where.visit(visitor);
		}
		if (group != null) {
			group.visit(visitor);
		}
		if (having != null) {
			having.visit(visitor);
		}
		if (union != null) {
			union.visit(visitor);
		}
		if (flag) {
			visitor.appendSQL(") ALL_CONTENT");
		}
		String sql = visitor.getSQL();
		Connection connection = null;
		PreparedStatement statement = null;
		ResultSet resultSet = null;
		try {
			connection = dataSource.getConnection();
			statement = connection.prepareStatement(sql);
			JDBCUtil.setStatementParams(statement, visitor.getParams());
			resultSet = statement.executeQuery();
			if (resultSet.next()) {
				return resultSet.getInt(1);
			}
			return 0;
		} catch (Throwable e) {
			throw Lang.wrapThrow(e, "Execute the query error, %s", sql);
		} finally {
			visitor.close();
			JDBCUtil.freeResource(connection, statement, resultSet);
		}
	}

	protected abstract SQLVisitor visitPaging(QueryParam queryParam);

	protected SQLVisitor visitSQL(QueryParam queryParam) {
		Column column = this.column;
		SQLVisitor visitor = new SQLVisitor(queryParam);
		if (isDistinct) {
			visitor.appendSQL("SELECT DISTINCT ");
		} else {
			visitor.appendSQL("SELECT ");
		}
		column.visitAlias(visitor);
		from.visit(visitor);
		if (join != null) {
			join.visit(visitor);
		}
		if (where != null) {
			where.visit(visitor);
		}
		if (group != null) {
			group.visit(visitor);
		}
		if (having != null) {
			having.visit(visitor);
		}
		if (union != null) {
			union.visit(visitor);
		}
		if (order != null) {
			order.visit(visitor);
		}
		return visitor;
	}

	public <T> T executeQuery(QueryParam queryParam, ResultSetCallback<T> callback) {

		SQLVisitor visitor = queryParam.isPaging() ? visitPaging(queryParam) : visitSQL(queryParam);

		String sql = visitor.getSQL();
		Connection connection = null;
		PreparedStatement statement = null;
		ResultSet resultSet = null;
		try {
			connection = dataSource.getConnection();
			statement = connection.prepareStatement(sql);
			JDBCUtil.setStatementParams(statement, visitor.getParams());
			resultSet = statement.executeQuery();
			return callback.getResult(resultSet, aliases);
		} catch (Throwable e) {
			throw Lang.wrapThrow(e, "Execute the query error, %s", sql);
		} finally {
			visitor.close();
			JDBCUtil.freeResource(connection, statement, resultSet);
		}

	}

	public boolean visitAsSubQuery(SQLVisitor visitor) {
		int sqlSize = visitor.getSqlSize();
		if (isDistinct) {
			visitor.appendSQL("(SELECT DISTINCT ");
		} else {
			visitor.appendSQL("(SELECT ");
		}
		column.visitSQL(visitor);
		from.visit(visitor);
		if (join != null) {
			join.visit(visitor);
		}
		if (where != null) {
			if (!where.matchVisit(visitor)) {
				visitor.back(sqlSize);
				return false;
			}
		}
		if (group != null) {
			group.visit(visitor);
		}
		if (having != null) {
			having.visit(visitor);
		}
		if (union != null) {
			union.visit(visitor);
		}
		if (order != null) {
			order.visit(visitor);
		}
		visitor.appendSQL(')');
		return true;
	}

	public void visitAsQuery(SQLVisitor visitor) {
		if (isDistinct) {
			visitor.appendSQL("SELECT DISTINCT ");
		} else {
			visitor.appendSQL("SELECT ");
		}
		column.visitSQL(visitor);
		from.visit(visitor);
		if (join != null) {
			join.visit(visitor);
		}
		if (where != null) {
			where.visit(visitor);
		}
		if (group != null) {
			group.visit(visitor);
		}
		if (having != null) {
			having.visit(visitor);
		}
		if (union != null) {
			union.visit(visitor);
		}
		if (order != null) {
			order.visit(visitor);
		}
	}

	public ArrayIterator<ColumnItem> getColumns() {
		return column.getColumnItems();
	}

	public String[] getAliases() {
		return aliases;
	}

}
